/*
    Copyright 2006 Andrew Wilkinson <andrew@indiegigs.co.uk>

    This file is part of libfixed.

    libfixed is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    libfixed is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with libfixed; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdint.h>
#include <math.h>

#ifndef __FIXED_H__
#define __FIXED_H__

#ifdef FIXED
#ifdef FIXED == 16
#define Fixed Fixed16
#define Fixed_sign Fixed_sign16
#define Fixed_mag Fixed_mag16
#define Fixed_frac Fixed_frac16
#define Fixed_create Fixed_create16
#define Fixed_createi Fixed_createi16
#define Fixed_add Fixed_add16
#define Fixed_addi Fixed_addi16
#define Fixed_sub Fixed_sub16
#define Fixed_subi Fixed_subi16
#define Fixed_mul Fixed_mul16
#define Fixed_muli Fixed_muli16
#define Fixed_div Fixed_div16
#define Fixed_divi Fixed_divi16
#define Fixed_neg Fixed_neg16

#define Fixed_lt Fixed_lt16
#define Fixed_le Fixed_le16
#define Fixed_eq Fixed_eq16
#define Fixed_ne Fixed_ne16
#define Fixed_ge Fixed_ge16
#define Fixed_gt Fixed_gt16

#define Fixed_fromFloat Fixed_fromFloat16
#define Fixed_fromDouble Fixed_fromDouble16
#define Fixed_toFloat Fixed_toFloat16
#define Fixed_toDouble Fixed_toDouble16
#define Fixed_round Fixed_round16
#define Fixed_dtor Fixed_dtor16
#define Fixed_pi Fixed_pi16
#define Fixed_e Fixed_e16
#define Fixed_zero Fixed_zero16
#define Fixed_one Fixed_one16

#define Fixed_sin Fixed_sin16
#define Fixed_cos Fixed_cos16
#define Fixed_sqrt Fixed_sqrt16

#elif FIXED == 32
#define Fixed Fixed32
#define Fixed_sign Fixed_sign32
#define Fixed_mag Fixed_mag32
#define Fixed_frac Fixed_frac32
#define Fixed_create Fixed_create32
#define Fixed_createi Fixed_createi32
#define Fixed_add Fixed_add32
#define Fixed_addi Fixed_addi32
#define Fixed_sub Fixed_sub32
#define Fixed_subi Fixed_subi32
#define Fixed_mul Fixed_mul32
#define Fixed_muli Fixed_muli32
#define Fixed_div Fixed_div32
#define Fixed_divi Fixed_divi32
#define Fixed_neg Fixed_neg32

#define Fixed_lt Fixed_lt32
#define Fixed_le Fixed_le32
#define Fixed_eq Fixed_eq32
#define Fixed_ne Fixed_ne32
#define Fixed_ge Fixed_ge32
#define Fixed_gt Fixed_gt32

#define Fixed_fromFloat Fixed_fromFloat32
#define Fixed_fromDouble Fixed_fromDouble32
#define Fixed_toFloat Fixed_toFloat32
#define Fixed_toDouble Fixed_toDouble32
#define Fixed_round Fixed_round32
#define Fixed_dtor Fixed_dtor32
#define Fixed_pi Fixed_pi32
#define Fixed_e Fixed_e32
#define Fixed_zero Fixed_zero32
#define Fixed_one Fixed_one32

#define Fixed_sin Fixed_sin32
#define Fixed_cos Fixed_cos32
#define Fixed_sqrt Fixed_sqrt32

#else
#error LibFixed only supports 16.16 and 32.32 types at the moment. Sorry.
#endif
#endif

int pow10i(int x);

struct Fixed16_t { int32_t val; };
typedef struct Fixed16_t Fixed16;
#define Fixed_sign16(x) (x.val < 0 ? -1 : 1)
#define Fixed_mag16(x) ((int16_t)(((x.val) < 0 ? -(x.val) : x.val) >> 16))
#define Fixed_frac16(x) ((uint16_t)(((x.val) < 0 ? -(x.val) : (x.val)) & (((int32_t)1 << 16)-1)))
#define Fixed_abs16(x) ((x).val < 0 ? (Fixed16){ -(x).val } : (x))

struct Fixed32_t { int64_t val; };
typedef struct Fixed32_t Fixed32;
#define Fixed_sign32(x) (x.val < 0 ? -1 : 1)
#define Fixed_mag32(x) ((int32_t)(((x).val < 0 ? -(x).val : x.val) >> 32))
#define Fixed_frac32(x) ((uint32_t)(((x).val < 0 ? -(x).val : (x).val) & (((int64_t)1 << 32)-1)))
#define Fixed_abs32(x) ((x).val < 0 ? (Fixed32){ -(x).val } : (x))

#define Fixed_createi16(mag) ((Fixed16) { (mag < 0 ? -(((int32_t)(-(mag))) << 16) : ((int32_t)(mag)) << 16) })
#define Fixed_createf16(mag, frac) ((Fixed16) { (mag < 0 ? -(((int32_t)(-(mag))) << 16 | (frac)) : ((int32_t)(mag)) << 16 | (frac)) })
#define Fixed_create16(mag, frac, digits) Fixed_createf16(mag, Fixed_divi16(Fixed_createi16(frac), pow10i(digits)).val)

#define Fixed_createi32(mag) ((Fixed32) { (mag < 0 ? -(((int64_t)(-(mag))) << 32) : ((int64_t)(mag)) << 32) })
#define Fixed_createf32(mag, frac) ((Fixed32) { (mag < 0 ? -(((int64_t)(-(mag))) << 32 | (frac)) : ((int64_t)(mag)) << 32 | (frac)) })
#define Fixed_create32(mag, frac, digits) Fixed_createf32(mag, Fixed_divi32(Fixed_createi32(frac), pow10i(digits)).val)

#define Fixed_add16(arg1, arg2) ((Fixed16) { ((arg1).val + (arg2).val) })
#define Fixed_add32(arg1, arg2) ((Fixed32) { ((arg1).val + (arg2).val) })
Fixed32 Fixed_add16_32(Fixed16 arg1, Fixed32 arg2);
Fixed32 Fixed_add32_16(Fixed32 arg1, Fixed16 arg2);

#define Fixed_addi16(arg1, arg2) ((Fixed16) { ((arg1).val + ((arg2) << 16)) })
#define Fixed_addi32(arg1, arg2) ((Fixed32) { ((arg1).val + ((int64_t)(arg2) << 32)) })

#define Fixed_sub16(arg1, arg2) ((Fixed16) { ((arg1).val - (arg2).val) } )
#define Fixed_sub32(arg1, arg2) ((Fixed32) { ((arg1).val - (arg2).val) } )
Fixed32 Fixed_sub16_32(Fixed16 arg1, Fixed32 arg2);
Fixed32 Fixed_sub32_16(Fixed32 arg1, Fixed16 arg2);

#define Fixed_subi16(arg1, arg2) ((Fixed16) { ((arg1).val - ((arg2) << 16)) })
#define Fixed_subi32(arg1, arg2) ((Fixed32) { ((arg1).val - ((int64_t)(arg2) << 32)) })

#define Fixed_mul16(arg1, arg2) ((Fixed16) { (((int64_t)arg1.val * (int64_t)arg2.val) >> 16) } )
Fixed32 Fixed_mul32(Fixed32 arg1, Fixed32 arg2);

//TODO: Fix hack where muli is implemented by converting int to fixed.
#define Fixed_muli16(arg1, arg2) (Fixed_mul16(arg1, Fixed_createi16(arg2)))
#define Fixed_muli32(arg1, arg2) (Fixed_mul32(arg1, Fixed_createi32(arg2)))

Fixed16 Fixed_div16(Fixed16 arg1, Fixed16 arg2);
Fixed32 Fixed_div32(Fixed32 arg1, Fixed32 arg2);

//TODO: Fix hack where divi is implemented by converting int to fixed.
#define Fixed_divi16(arg1, arg2) (Fixed_div16(arg1, Fixed_createi16(arg2)))
#define Fixed_divi32(arg1, arg2) (Fixed_div32(arg1, Fixed_createi32(arg2)))

#define Fixed_neg16(arg1) ((Fixed16) { - (arg1).val} )
#define Fixed_neg32(arg1) ((Fixed32) { - (arg1).val} )

#define Fixed_gt16(arg1, arg2) ((arg1).val > (arg2).val)
#define Fixed_ge16(arg1, arg2) ((arg1).val >= (arg2).val)
#define Fixed_eq16(arg1, arg2) ((arg1).val == (arg2).val)
#define Fixed_ne16(arg1, arg2) ((arg1).val != (arg2).val)
#define Fixed_le16(arg1, arg2) ((arg1).val <= (arg2).val)
#define Fixed_lt16(arg1, arg2) ((arg1).val < (arg2).val)

#define Fixed_gt32(arg1, arg2) ((arg1).val > (arg2).val)
#define Fixed_ge32(arg1, arg2) ((arg1).val >= (arg2).val)
#define Fixed_eq32(arg1, arg2) ((arg1).val == (arg2).val)
#define Fixed_ne32(arg1, arg2) ((arg1).val != (arg2).val)
#define Fixed_le32(arg1, arg2) ((arg1).val <= (arg2).val)
#define Fixed_lt32(arg1, arg2) ((arg1).val < (arg2).val)

Fixed16 Fixed_fromFloat16(float f);
Fixed16 Fixed_fromDouble16(double f);
float Fixed_toFloat16(Fixed16 f);
double Fixed_toDouble16(Fixed16 f);

Fixed32 Fixed_fromFloat32(float f);
Fixed32 Fixed_fromDouble32(double f);
float Fixed_toFloat32(Fixed32 f);
double Fixed_toDouble32(Fixed32 f);

static inline int Fixed_round16(Fixed16 x) {
    if(x.val < 0) {
        if(Fixed_frac16(x) >= 0x7fff) {
            return -Fixed_mag16(x) - 1;
        } else {
            return -Fixed_mag16(x);
        }
    } else {
        if(Fixed_frac16(x) > 0x7fff) {
            return Fixed_mag16(x) + 1;
        } else {
            return Fixed_mag16(x);
        }
    }
}
#define Fixed_round32(x) (Fixed_frac32(x) >= 0x7fffffff ? Fixed_mag32(x) + 1 : Fixed_mag32(x))

extern Fixed16 Fixed_pi16;
extern Fixed32 Fixed_pi32;
extern Fixed16 Fixed_e16;
extern Fixed32 Fixed_e32;
extern Fixed16 Fixed_zero16;
extern Fixed32 Fixed_zero32;
extern Fixed16 Fixed_half16;
extern Fixed32 Fixed_half32;
extern Fixed16 Fixed_one16;
extern Fixed32 Fixed_one32;

Fixed16 Fixed_dtor16(Fixed16 r);
Fixed32 Fixed_dtor32(Fixed32 r);

Fixed16 Fixed_sin16(Fixed16 angle);
Fixed16 Fixed_cos16(Fixed16 angle);
Fixed32 Fixed_sin32(Fixed32 angle);
Fixed32 Fixed_cos32(Fixed32 angle);

Fixed16 Fixed_sqrt16(Fixed16 value);
Fixed32 Fixed_sqrt32(Fixed32 value);

Fixed16 Fixed_pow16(Fixed16 base, Fixed16 exponent);
Fixed32 Fixed_pow32(Fixed32 base, Fixed32 exponent);

Fixed16 Fixed_log16(Fixed16 exponent);
Fixed32 Fixed_log32(Fixed32 exponent);

Fixed16 Fixed_exp16(Fixed16 exponent);
Fixed32 Fixed_exp32(Fixed32 exponent);

static inline Fixed32 Fixed_convert16to32(Fixed16 val) { return Fixed_createf32(Fixed_mag16(val), Fixed_frac16(val)); }

#endif
